1 /*******************************************************************************
2 * Copyright (c) 2000, 2016 IBM Corporation and others.
3 *
4 * This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
8 *
9 * SPDX-License-Identifier: EPL-2.0
10 *
11 * Contributors:
12 * IBM Corporation - initial API and implementation
13 *******************************************************************************/
14 package org.eclipse.swt.custom;
15
16 import org.eclipse.swt.*;
17 import org.eclipse.swt.accessibility.*;
18 import org.eclipse.swt.events.*;
19 import org.eclipse.swt.graphics.*;
20 import org.eclipse.swt.widgets.*;
21
22 /**
23 * A TableCursor provides a way for the user to navigate around a Table
24 * using the keyboard. It also provides a mechanism for selecting an
25 * individual cell in a table.
26 * <p>
27 * For a detailed example of using a TableCursor to navigate to a cell and then edit it see
28 * http://git.eclipse.org/c/platform/eclipse.platform.swt.git/tree/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet96.java .
29 *
30 * <dl>
31 * <dt><b>Styles:</b></dt>
32 * <dd>BORDER</dd>
33 * <dt><b>Events:</b></dt>
34 * <dd>Selection, DefaultSelection</dd>
35 * </dl>
36 *
37 * @since 2.0
38 *
39 * @see <a href="http://www.eclipse.org/swt/snippets/#tablecursor">TableCursor snippets</a>
40 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
41 */
42 public class TableCursor extends Canvas {
43 Table table;
44 TableItem row = null;
45 TableColumn column = null;
46 Listener listener, tableListener, resizeListener, disposeItemListener, disposeColumnListener;
47
48 Color background = null;
49 Color foreground = null;
50
51 /* By default, invert the list selection colors */
52 static final int BACKGROUND = SWT.COLOR_LIST_SELECTION_TEXT;
53 static final int FOREGROUND = SWT.COLOR_LIST_SELECTION;
54
55 /**
56 * Constructs a new instance of this class given its parent
57 * table and a style value describing its behavior and appearance.
58 * <p>
59 * The style value is either one of the style constants defined in
60 * class <code>SWT</code> which is applicable to instances of this
61 * class, or must be built by <em>bitwise OR</em>'ing together
62 * (that is, using the <code>int</code> "|" operator) two or more
63 * of those <code>SWT</code> style constants. The class description
64 * lists the style constants that are applicable to the class.
65 * Style bits are also inherited from superclasses.
66 * </p>
67 *
68 * @param parent a Table control which will be the parent of the new instance (cannot be null)
69 * @param style the style of control to construct
70 *
71 * @exception IllegalArgumentException <ul>
72 * <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
73 * </ul>
74 * @exception SWTException <ul>
75 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
76 * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
77 * </ul>
78 *
79 * @see SWT#BORDER
80 * @see Widget#checkSubclass()
81 * @see Widget#getStyle()
82 */
TableCursor(Table parent, int style)83 public TableCursor(Table parent, int style) {
84 super(parent, style);
85 table = parent;
86 setBackground(null);
87 setForeground(null);
88
89 listener = event -> {
90 switch (event.type) {
91 case SWT.Dispose :
92 onDispose(event);
93 break;
94 case SWT.FocusIn :
95 case SWT.FocusOut :
96 redraw();
97 break;
98 case SWT.KeyDown :
99 keyDown(event);
100 break;
101 case SWT.Paint :
102 paint(event);
103 break;
104 case SWT.Traverse : {
105 event.doit = true;
106 switch (event.detail) {
107 case SWT.TRAVERSE_ARROW_NEXT :
108 case SWT.TRAVERSE_ARROW_PREVIOUS :
109 case SWT.TRAVERSE_RETURN :
110 event.doit = false;
111 break;
112 }
113 break;
114 }
115 }
116 };
117 int[] events = new int[] {SWT.Dispose, SWT.FocusIn, SWT.FocusOut, SWT.KeyDown, SWT.Paint, SWT.Traverse};
118 for (int event : events) {
119 addListener(event, listener);
120 }
121
122 tableListener = event -> {
123 switch (event.type) {
124 case SWT.MouseDown :
125 tableMouseDown(event);
126 break;
127 case SWT.FocusIn :
128 tableFocusIn(event);
129 break;
130 }
131 };
132 table.addListener(SWT.FocusIn, tableListener);
133 table.addListener(SWT.MouseDown, tableListener);
134
135 disposeItemListener = event -> {
136 unhookRowColumnListeners();
137 row = null;
138 column = null;
139 _resize();
140 };
141 disposeColumnListener = event -> {
142 unhookRowColumnListeners();
143 row = null;
144 column = null;
145 _resize();
146 };
147 resizeListener = event -> _resize();
148 ScrollBar hBar = table.getHorizontalBar();
149 if (hBar != null) {
150 hBar.addListener(SWT.Selection, resizeListener);
151 }
152 ScrollBar vBar = table.getVerticalBar();
153 if (vBar != null) {
154 vBar.addListener(SWT.Selection, resizeListener);
155 }
156
157 getAccessible().addAccessibleControlListener(new AccessibleControlAdapter() {
158 @Override
159 public void getRole(AccessibleControlEvent e) {
160 e.detail = ACC.ROLE_TABLECELL;
161 }
162 });
163 getAccessible().addAccessibleListener(new AccessibleAdapter() {
164 @Override
165 public void getName(AccessibleEvent e) {
166 if (row == null) return;
167 int columnIndex = column == null ? 0 : table.indexOf(column);
168 e.result = row.getText(columnIndex);
169 }
170 });
171 }
172
173 /**
174 * Adds the listener to the collection of listeners who will
175 * be notified when the user changes the receiver's selection, by sending
176 * it one of the messages defined in the <code>SelectionListener</code>
177 * interface.
178 * <p>
179 * When <code>widgetSelected</code> is called, the item field of the event object is valid.
180 * If the receiver has <code>SWT.CHECK</code> style set and the check selection changes,
181 * the event object detail field contains the value <code>SWT.CHECK</code>.
182 * <code>widgetDefaultSelected</code> is typically called when an item is double-clicked.
183 * </p>
184 *
185 * @param listener the listener which should be notified when the user changes the receiver's selection
186 *
187 * @exception IllegalArgumentException <ul>
188 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
189 * </ul>
190 * @exception SWTException <ul>
191 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
192 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
193 * </ul>
194 *
195 * @see SelectionListener
196 * @see SelectionEvent
197 * @see #removeSelectionListener(SelectionListener)
198 *
199 */
addSelectionListener(SelectionListener listener)200 public void addSelectionListener(SelectionListener listener) {
201 checkWidget();
202 if (listener == null)
203 SWT.error(SWT.ERROR_NULL_ARGUMENT);
204 TypedListener typedListener = new TypedListener(listener);
205 addListener(SWT.Selection, typedListener);
206 addListener(SWT.DefaultSelection, typedListener);
207 }
208
onDispose(Event event)209 void onDispose(Event event) {
210 removeListener(SWT.Dispose, listener);
211 notifyListeners(SWT.Dispose, event);
212 event.type = SWT.None;
213
214 table.removeListener(SWT.FocusIn, tableListener);
215 table.removeListener(SWT.MouseDown, tableListener);
216 unhookRowColumnListeners();
217 ScrollBar hBar = table.getHorizontalBar();
218 if (hBar != null) {
219 hBar.removeListener(SWT.Selection, resizeListener);
220 }
221 ScrollBar vBar = table.getVerticalBar();
222 if (vBar != null) {
223 vBar.removeListener(SWT.Selection, resizeListener);
224 }
225 }
226
keyDown(Event event)227 void keyDown(Event event) {
228 if (row == null) return;
229 switch (event.character) {
230 case SWT.CR :
231 notifyListeners(SWT.DefaultSelection, new Event());
232 return;
233 }
234 int rowIndex = table.indexOf(row);
235 int columnIndex = column == null ? 0 : table.indexOf(column);
236 switch (event.keyCode) {
237 case SWT.ARROW_UP :
238 setRowColumn(Math.max(0, rowIndex - 1), columnIndex, true);
239 break;
240 case SWT.ARROW_DOWN :
241 setRowColumn(Math.min(rowIndex + 1, table.getItemCount() - 1), columnIndex, true);
242 break;
243 case SWT.ARROW_LEFT :
244 case SWT.ARROW_RIGHT :
245 {
246 int columnCount = table.getColumnCount();
247 if (columnCount == 0) break;
248 int[] order = table.getColumnOrder();
249 int index = 0;
250 while (index < order.length) {
251 if (order[index] == columnIndex) break;
252 index++;
253 }
254 if (index == order.length) index = 0;
255 int leadKey = (getStyle() & SWT.RIGHT_TO_LEFT) != 0 ? SWT.ARROW_RIGHT : SWT.ARROW_LEFT;
256 if (event.keyCode == leadKey) {
257 setRowColumn(rowIndex, order[Math.max(0, index - 1)], true);
258 } else {
259 setRowColumn(rowIndex, order[Math.min(columnCount - 1, index + 1)], true);
260 }
261 break;
262 }
263 case SWT.HOME :
264 setRowColumn(0, columnIndex, true);
265 break;
266 case SWT.END :
267 {
268 int i = table.getItemCount() - 1;
269 setRowColumn(i, columnIndex, true);
270 break;
271 }
272 case SWT.PAGE_UP :
273 {
274 int index = table.getTopIndex();
275 if (index == rowIndex) {
276 Rectangle rect = table.getClientArea();
277 TableItem item = table.getItem(index);
278 Rectangle itemRect = item.getBounds(0);
279 rect.height -= itemRect.y;
280 int height = table.getItemHeight();
281 int page = Math.max(1, rect.height / height);
282 index = Math.max(0, index - page + 1);
283 }
284 setRowColumn(index, columnIndex, true);
285 break;
286 }
287 case SWT.PAGE_DOWN :
288 {
289 int index = table.getTopIndex();
290 Rectangle rect = table.getClientArea();
291 TableItem item = table.getItem(index);
292 Rectangle itemRect = item.getBounds(0);
293 rect.height -= itemRect.y;
294 int height = table.getItemHeight();
295 int page = Math.max(1, rect.height / height);
296 int end = table.getItemCount() - 1;
297 index = Math.min(end, index + page - 1);
298 if (index == rowIndex) {
299 index = Math.min(end, index + page - 1);
300 }
301 setRowColumn(index, columnIndex, true);
302 break;
303 }
304 }
305 }
306
paint(Event event)307 void paint(Event event) {
308 if (row == null) return;
309 int columnIndex = column == null ? 0 : table.indexOf(column);
310 GC gc = event.gc;
311 gc.setBackground(getBackground());
312 gc.setForeground(getForeground());
313 gc.fillRectangle(event.x, event.y, event.width, event.height);
314 int x = 0;
315 Point size = getSize();
316 Image image = row.getImage(columnIndex);
317 if (image != null) {
318 Rectangle imageSize = image.getBounds();
319 int imageY = (size.y - imageSize.height) / 2;
320 gc.drawImage(image, x, imageY);
321 x += imageSize.width;
322 }
323 String text = row.getText(columnIndex);
324 if (text.length() > 0) {
325 Rectangle bounds = row.getBounds(columnIndex);
326 Point extent = gc.stringExtent(text);
327 // Temporary code - need a better way to determine table trim
328 String platform = SWT.getPlatform();
329 if ("win32".equals(platform)) { //$NON-NLS-1$
330 if (table.getColumnCount() == 0 || columnIndex == 0) {
331 x += 2;
332 } else {
333 int alignmnent = column.getAlignment();
334 switch (alignmnent) {
335 case SWT.LEFT:
336 x += 6;
337 break;
338 case SWT.RIGHT:
339 x = bounds.width - extent.x - 6;
340 break;
341 case SWT.CENTER:
342 x += (bounds.width - x - extent.x) / 2;
343 break;
344 }
345 }
346 } else {
347 if (table.getColumnCount() == 0) {
348 x += 5;
349 } else {
350 int alignmnent = column.getAlignment();
351 switch (alignmnent) {
352 case SWT.LEFT:
353 x += 5;
354 break;
355 case SWT.RIGHT:
356 x = bounds.width- extent.x - 2;
357 break;
358 case SWT.CENTER:
359 x += (bounds.width - x - extent.x) / 2 + 2;
360 break;
361 }
362 }
363 }
364 int textY = (size.y - extent.y) / 2;
365 gc.drawString(text, x, textY);
366 }
367 if (isFocusControl()) {
368 Display display = getDisplay();
369 gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
370 gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
371 gc.drawFocus(0, 0, size.x, size.y);
372 }
373 }
374
tableFocusIn(Event event)375 void tableFocusIn(Event event) {
376 if (isDisposed()) return;
377 if (isVisible()) {
378 if (row == null && column == null) return;
379 setFocus();
380 }
381 }
382
tableMouseDown(Event event)383 void tableMouseDown(Event event) {
384 if (isDisposed() || !isVisible()) return;
385 Point pt = new Point(event.x, event.y);
386 int lineWidth = table.getLinesVisible() ? table.getGridLineWidth() : 0;
387 TableItem item = table.getItem(pt);
388 if ((table.getStyle() & SWT.FULL_SELECTION) != 0) {
389 if (item == null) return;
390 } else {
391 int start = item != null ? table.indexOf(item) : table.getTopIndex();
392 int end = table.getItemCount();
393 Rectangle clientRect = table.getClientArea();
394 for (int i = start; i < end; i++) {
395 TableItem nextItem = table.getItem(i);
396 Rectangle rect = nextItem.getBounds(0);
397 if (pt.y >= rect.y && pt.y < rect.y + rect.height + lineWidth) {
398 item = nextItem;
399 break;
400 }
401 if (rect.y > clientRect.y + clientRect.height) return;
402 }
403 if (item == null) return;
404 }
405 TableColumn newColumn = null;
406 int columnCount = table.getColumnCount();
407 if (columnCount == 0) {
408 if ((table.getStyle() & SWT.FULL_SELECTION) == 0) {
409 Rectangle rect = item.getBounds(0);
410 rect.width += lineWidth;
411 rect.height += lineWidth;
412 if (!rect.contains(pt)) return;
413 }
414 } else {
415 for (int i = 0; i < columnCount; i++) {
416 Rectangle rect = item.getBounds(i);
417 rect.width += lineWidth;
418 rect.height += lineWidth;
419 if (rect.contains(pt)) {
420 newColumn = table.getColumn(i);
421 break;
422 }
423 }
424 if (newColumn == null) {
425 if ((table.getStyle() & SWT.FULL_SELECTION) == 0) return;
426 newColumn = table.getColumn(0);
427 }
428 }
429 setRowColumn(item, newColumn, true);
430 setFocus();
431 return;
432 }
setRowColumn(int row, int column, boolean notify)433 void setRowColumn(int row, int column, boolean notify) {
434 TableItem item = row == -1 ? null : table.getItem(row);
435 TableColumn col = column == -1 || table.getColumnCount() == 0 ? null : table.getColumn(column);
436 setRowColumn(item, col, notify);
437 }
setRowColumn(TableItem row, TableColumn column, boolean notify)438 void setRowColumn(TableItem row, TableColumn column, boolean notify) {
439 if (this.row == row && this.column == column) {
440 return;
441 }
442 if (this.row != null && this.row != row) {
443 this.row.removeListener(SWT.Dispose, disposeItemListener);
444 this.row = null;
445 }
446 if (this.column != null && this.column != column) {
447 this.column.removeListener(SWT.Dispose, disposeColumnListener);
448 this.column.removeListener(SWT.Move, resizeListener);
449 this.column.removeListener(SWT.Resize, resizeListener);
450 this.column = null;
451 }
452 if (row != null) {
453 if (this.row != row) {
454 this.row = row;
455 row.addListener(SWT.Dispose, disposeItemListener);
456 table.showItem(row);
457 }
458 if (this.column != column && column != null) {
459 this.column = column;
460 column.addListener(SWT.Dispose, disposeColumnListener);
461 column.addListener(SWT.Move, resizeListener);
462 column.addListener(SWT.Resize, resizeListener);
463 table.showColumn(column);
464 }
465 int columnIndex = column == null ? 0 : table.indexOf(column);
466 setBounds(row.getBounds(columnIndex));
467 redraw();
468 if (notify) {
469 notifyListeners(SWT.Selection, new Event());
470 }
471 }
472 getAccessible().setFocus(ACC.CHILDID_SELF);
473 }
474
475 @Override
setVisible(boolean visible)476 public void setVisible(boolean visible) {
477 checkWidget();
478 if (visible) _resize();
479 super.setVisible(visible);
480 }
481
482 /**
483 * Removes the listener from the collection of listeners who will
484 * be notified when the user changes the receiver's selection.
485 *
486 * @param listener the listener which should no longer be notified
487 *
488 * @exception IllegalArgumentException <ul>
489 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
490 * </ul>
491 * @exception SWTException <ul>
492 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
493 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
494 * </ul>
495 *
496 * @see SelectionListener
497 * @see #addSelectionListener(SelectionListener)
498 *
499 * @since 3.0
500 */
removeSelectionListener(SelectionListener listener)501 public void removeSelectionListener(SelectionListener listener) {
502 checkWidget();
503 if (listener == null) {
504 SWT.error(SWT.ERROR_NULL_ARGUMENT);
505 }
506 removeListener(SWT.Selection, listener);
507 removeListener(SWT.DefaultSelection, listener);
508 }
509
_resize()510 void _resize() {
511 if (row == null) {
512 setBounds(-200, -200, 0, 0);
513 } else {
514 int columnIndex = column == null ? 0 : table.indexOf(column);
515 setBounds(row.getBounds(columnIndex));
516 }
517 }
518 /**
519 * Returns the index of the column over which the TableCursor is positioned.
520 *
521 * @return the column index for the current position
522 *
523 * @exception SWTException <ul>
524 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
525 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
526 * </ul>
527 */
getColumn()528 public int getColumn() {
529 checkWidget();
530 return column == null ? 0 : table.indexOf(column);
531 }
532 /**
533 * Returns the background color that the receiver will use to draw.
534 *
535 * @return the receiver's background color
536 */
537 @Override
getBackground()538 public Color getBackground() {
539 checkWidget();
540 if (background == null) {
541 return getDisplay().getSystemColor(BACKGROUND);
542 }
543 return background;
544 }
545 /**
546 * Returns the foreground color that the receiver will use to draw.
547 *
548 * @return the receiver's foreground color
549 */
550 @Override
getForeground()551 public Color getForeground() {
552 checkWidget();
553 if (foreground == null) {
554 return getDisplay().getSystemColor(FOREGROUND);
555 }
556 return foreground;
557 }
558 /**
559 * Returns the row over which the TableCursor is positioned.
560 *
561 * @return the item for the current position, or <code>null</code> if none
562 *
563 * @exception SWTException <ul>
564 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
565 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
566 * </ul>
567 */
getRow()568 public TableItem getRow() {
569 checkWidget();
570 return row;
571 }
572 /**
573 * Sets the receiver's background color to the color specified
574 * by the argument, or to the default system color for the control
575 * if the argument is null.
576 * <p>
577 * Note: This operation is a hint and may be overridden by the platform.
578 * For example, on Windows the background of a Button cannot be changed.
579 * </p>
580 * @param color the new color (or null)
581 *
582 * @exception IllegalArgumentException <ul>
583 * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
584 * </ul>
585 * @exception SWTException <ul>
586 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
587 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
588 * </ul>
589 */
590 @Override
setBackground(Color color)591 public void setBackground (Color color) {
592 background = color;
593 super.setBackground(getBackground());
594 redraw();
595 }
596 /**
597 * Sets the receiver's foreground color to the color specified
598 * by the argument, or to the default system color for the control
599 * if the argument is null.
600 * <p>
601 * Note: This operation is a hint and may be overridden by the platform.
602 * </p>
603 * @param color the new color (or null)
604 *
605 * @exception IllegalArgumentException <ul>
606 * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
607 * </ul>
608 * @exception SWTException <ul>
609 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
610 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
611 * </ul>
612 */
613 @Override
setForeground(Color color)614 public void setForeground (Color color) {
615 foreground = color;
616 super.setForeground(getForeground());
617 redraw();
618 }
619 /**
620 * Positions the TableCursor over the cell at the given row and column in the parent table.
621 *
622 * @param row the index of the row for the cell to select
623 * @param column the index of column for the cell to select
624 *
625 * @exception SWTException <ul>
626 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
627 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
628 * </ul>
629 *
630 */
setSelection(int row, int column)631 public void setSelection(int row, int column) {
632 checkWidget();
633 int columnCount = table.getColumnCount();
634 int maxColumnIndex = columnCount == 0 ? 0 : columnCount - 1;
635 if (row < 0
636 || row >= table.getItemCount()
637 || column < 0
638 || column > maxColumnIndex)
639 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
640 setRowColumn(row, column, false);
641 }
642 /**
643 * Positions the TableCursor over the cell at the given row and column in the parent table.
644 *
645 * @param row the TableItem of the row for the cell to select
646 * @param column the index of column for the cell to select
647 *
648 * @exception SWTException <ul>
649 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
650 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
651 * </ul>
652 *
653 */
setSelection(TableItem row, int column)654 public void setSelection(TableItem row, int column) {
655 checkWidget();
656 int columnCount = table.getColumnCount();
657 int maxColumnIndex = columnCount == 0 ? 0 : columnCount - 1;
658 if (row == null
659 || row.isDisposed()
660 || column < 0
661 || column > maxColumnIndex)
662 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
663 setRowColumn(table.indexOf(row), column, false);
664 }
unhookRowColumnListeners()665 void unhookRowColumnListeners() {
666 if (column != null) {
667 column.removeListener(SWT.Dispose, disposeColumnListener);
668 column.removeListener(SWT.Move, resizeListener);
669 column.removeListener(SWT.Resize, resizeListener);
670 column = null;
671 }
672 if (row != null) {
673 row.removeListener(SWT.Dispose, disposeItemListener);
674 row = null;
675 }
676 }
677 }
678